package com.boot.mall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.boot.common.constant.SecKillConstant;
import com.boot.common.to.seckill.SecKillOrderTo;
import com.boot.common.to.seckill.SecKillSessionTo;
import com.boot.common.to.seckill.SecKillSkuRelation;
import com.boot.common.to.seckill.SkuInfoTo;
import com.boot.common.vo.MemberResponseVo;
import com.boot.mall.seckill.feign.CouponFeignService;
import com.boot.mall.seckill.feign.ProductFeignService;
import com.boot.mall.seckill.interceptor.LoginInterceptor;
import com.boot.mall.seckill.mq.JmsProducer;
import com.boot.mall.seckill.service.SecKillService;
import com.boot.mall.seckill.vo.SecKillSkuRedisVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SecKillServiceImpl implements SecKillService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private JmsProducer jmsProducer;

    @Override
    public void UpSecKillSkuScheduled() {

        // 1.查询最近3天需要上架的活动
        List<SecKillSessionTo> relations = couponFeignService.getNearThreeDaysSecKillSession();
        // 2.将查询的结果缓存到Redis中
        if (relations != null && relations.size() > 0){
            for (SecKillSessionTo relation : relations) {
                // 抽取当前秒杀场次关联的Sku的id集合
                List<String> skuIds = relation.getRelationSkuList().stream().map(item -> item.getSkuId().toString()).collect(Collectors.toList());
                // 3.缓存活动信息
                saveSecKillSessionInfo(relation,skuIds);
                // 4.缓存活动关联的秒杀商品信息
                saveSecKillSessionRelationSku(relation,skuIds);
            }
        }

    }

    /**
     * 查询当前时间段的可以参与秒杀的场次
     * @return
     */
    @Override
    public List<SecKillSkuRedisVo> getCurrentSecKillSessionSku() {
        // 1.查询redis中所有秒杀场次信息
        Set<String> keys = stringRedisTemplate.keys(SecKillConstant.SEC_KILL_SESSION_PREFIX + "*");
        long nowTime = new Date().getTime();
        for (String key : keys) {
            // 2.去除key的开头和'_'
            String[] split = key.replace(SecKillConstant.SEC_KILL_SESSION_PREFIX, "").split("_");
            Long start = Long.parseLong(split[0]);  //开始时间
            Long end = Long.parseLong(split[1]);    //结束时间
            // 3.判断当前时间是否有秒杀场次
            if (nowTime >= start && nowTime <= end){
                // 4.当前时间在这个秒杀场次内，获取当前秒杀场次的商品id集合
                List<String> skuKeys = stringRedisTemplate.opsForList().range(key, -100, 100);
                // 5.绑定操作商品的hash
                BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SecKillConstant.SEC_KILL_SKU_PREFIX);
                List<String> objects = ops.multiGet(skuKeys);    //从redis中获取sku的信息集合
                // 6.判空并且返回可秒杀的商品
                if (objects != null && objects.size() > 0){
                    List<SecKillSkuRedisVo> list = objects.stream().map(item -> {
                        // 将object类型转换
                        SecKillSkuRedisVo vo = JSON.parseObject(item, SecKillSkuRedisVo.class);
                        return vo;
                    }).collect(Collectors.toList());
                    return list;
                }
            }
        }
        return null;
    }

    /**
     * 远程调用接口，用以查询sku是否在秒杀时间段内
     * @return
     * @param skuId
     */
    @Override
    public SecKillSkuRedisVo getSkuSecKillInfo(Long skuId) {
        // 1.查询所有参与秒杀的商品的key集合
        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SecKillConstant.SEC_KILL_SKU_PREFIX);
        Set<String> keys = ops.keys();
        Long min = new Date().getTime();  //计算最小时间，忽略已经过时间的秒杀场次
        String res = "";  //记录下标
        if (keys != null && keys.size() > 0) {
            // 1_3  2_3  3_3 4_3 这里一共四场3号商品的秒杀场次，一个商品不可以同时处于两个场次秒杀阶段当中
            String regx = "\\d_" + skuId.toString();
            // 遍历所有的keys
            for (String key : keys) {
                if (Pattern.matches(regx,key)){
                    // 将秒杀商品的JSON形式转换为对象
                    String json = ops.get(key);
                    SecKillSkuRedisVo vo = JSON.parseObject(json, SecKillSkuRedisVo.class);
                    // 获取该场次的秒杀开始和结束时间并进行比较
                    Long startTime = vo.getStartTime();
                    Long endTime = vo.getEndTime();
                    long nowTime = new Date().getTime();
                    // 当前场次的该商品已经结束秒杀，直接遍历下一个
                    if (nowTime > endTime){
                        continue;
                    }
                    // 当前商品正在处于秒杀阶段，直接返回vo信息
                    if (nowTime >= startTime && nowTime <= endTime){
                        return vo;
                    }else {
                        // 当前商品不处于秒杀阶段
                        Long temp = startTime - nowTime;
                        if (temp < min){
                            // 当min值更小时，res才会重新赋值
                            min = temp;
                            res = key;
                        }
                    }
                }
            }
            //执行到此处，说明此件商品还未开始秒杀，获取秒杀商品信息，并将token值置空
            String json = ops.get(res);
            SecKillSkuRedisVo vo = JSON.parseObject(json, SecKillSkuRedisVo.class);
            vo.setToken("");
            return vo;
        }
        return null;
    }

    /**
     * 进行秒杀的接口,秒杀成功返回一个订单号，秒杀失败返回null
     * @param killId
     * @param key
     * @param num
     * @return
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        // killId = 7_1 这样的，可能需要修改
        // 绑定hash对象
        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SecKillConstant.SEC_KILL_SKU_PREFIX);
        // 登录的用户信息
        MemberResponseVo member = LoginInterceptor.memberInfo.get();

        String json = ops.get(killId);  //获取当前秒杀kid的秒杀信息
        // 1.校验是否含有当前商品的秒杀信息，没有则返回null
        if (StringUtils.isEmpty(json)){
            return null;
        }
        // 将秒杀信息JSON格式转换为对象
        SecKillSkuRedisVo vo = JSON.parseObject(json, SecKillSkuRedisVo.class);
        // 2.校验时间的合法性，当前时间必须在秒杀时间段内
        Long startTime = vo.getStartTime();
        Long endTime = vo.getEndTime();
        long nowTime = new Date().getTime();
        // 不在秒杀时间段内
        if (!(nowTime >= startTime && nowTime <= endTime)){
            return null;
        }
        // 3.校验随机码
        String token = vo.getToken();
        if (!key.equals(token)){
            return null;
        }
        // 4.检验购买量是否合法
        if (num > vo.getRelation().getSeckillLimit().intValue()){
            return null;
        }
        // 5.判断当前用户是否已经购买过，包转秒杀的幂等性
        String memberKey = member.getId() + "_" + killId;
        Long ttl = endTime - nowTime;   //秒杀结束
        // 当前用户在redis中有数据，表示已经秒杀过当前商品
        Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(memberKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
        if (ifAbsent){
            return null;
        }
        // 6.所有验证通过，尝试秒杀
        RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_KILL_STOCK_SEMAPHORE + token);
        try {
            semaphore.tryAcquire(num,100,TimeUnit.MILLISECONDS);
            // 秒杀成功，给MQ发送消息创建订单
            String orderSn = IdWorker.getTimeId();
            SecKillOrderTo orderTo = new SecKillOrderTo();
            orderTo.setMemberId(member.getId());    //会员id
            orderTo.setNum(num);    //购买数量
            orderTo.setOrderSn(orderSn);    //订单号
            orderTo.setSeckillPrice(vo.getRelation().getSeckillPrice().intValue()); //秒杀价
            String[] split = killId.split("_");
            orderTo.setPromotionSessionId(Long.parseLong(split[0]));
            orderTo.setSkuId(Long.parseLong(split[1]));

            jmsProducer.sendMessage(orderTo);

            return orderSn;
        } catch (InterruptedException e) {
            // 秒杀失败返回null
            return null;
        }

    }



    /**
     * 将秒杀场次信息缓存到Redis中
     */
    private void saveSecKillSessionInfo(SecKillSessionTo relation,List<String> skuIds){
        // 将场次信息的Value值设置为  场次id + skuId
        List<String> collect = skuIds.stream().map(item -> relation.getId() + "_" + item).collect(Collectors.toList());
        long start = relation.getStartTime().getTime();
        long end = relation.getEndTime().getTime();
        // 开始时间到结束时间为key值
        String key = SecKillConstant.SEC_KILL_SESSION_PREFIX + start + "_" + end;
        if (!stringRedisTemplate.hasKey(key)) {
            // 缓存到Redis中
            stringRedisTemplate.opsForList().leftPushAll(key, collect);
        }
    }

    /**
     * 将秒杀场次关联的商品信息缓存到Redis中  key = 场次id + skuId
     */
    private void saveSecKillSessionRelationSku(SecKillSessionTo relation,List<String> skuIds){

        // 远程查询skuIds的商品基本属性
        Map<String, Object> map = productFeignService.getSkuInfoBySkuIds(skuIds);

        // 绑定hash对象
        BoundHashOperations<String, Object, Object> options = stringRedisTemplate.boundHashOps(SecKillConstant.SEC_KILL_SKU_PREFIX);

        List<SecKillSkuRelation> relationSkuList = relation.getRelationSkuList();
        for (SecKillSkuRelation skuRelation : relationSkuList) {

            String token = UUID.randomUUID().toString().replaceAll("-", "");

            // key的值为  场次id + 商品id  例如：2_4  2号场次4号商品
            String key = relation.getId() + "_" + skuRelation.getSkuId().toString();

            // 判断当前秒杀商品是否已经缓存，如果未缓存则需要执行下列操作进行缓存，否则直接跳过
            if (!options.hasKey(key)) {

                // 创建存储到redis中的秒杀商品对象
                SecKillSkuRedisVo redisVo = new SecKillSkuRedisVo();

                // 秒杀商品的秒杀信息
                SecKillSkuRelation relationInfo = new SecKillSkuRelation();
                BeanUtils.copyProperties(skuRelation, relationInfo);
                redisVo.setRelation(relationInfo);

                // 秒杀商品的基本信息
                Object object = map.get(skuRelation.getSkuId().toString());
                System.out.println(object);
                if (object != null) {
                    ObjectMapper mapper = new ObjectMapper();
                    SkuInfoTo skuInfoTo = mapper.convertValue(object, SkuInfoTo.class);
                    redisVo.setSkuInfoTo(skuInfoTo);
                }

                // 秒杀的开始和结束时间
                redisVo.setStartTime(relation.getStartTime().getTime());
                redisVo.setEndTime(relation.getEndTime().getTime());

                // 设置秒杀商品的随机令牌
                redisVo.setToken(token);

                // 设置每个商品的信号量，需要判断当前商品的信号量是否已经设置

                // 当前秒杀的商品信号量不存在，则新设置信号量
                RSemaphore stock = redissonClient.getSemaphore(SecKillConstant.SEC_KILL_STOCK_SEMAPHORE + token);
                stock.trySetPermits(skuRelation.getSeckillCount().intValue());

                String json = JSON.toJSONString(redisVo);
                options.put(key, json);
            }
        }
    }

}
